上回說明了 IOC 與 DI 的概念與基本實作方式,並在文末提及每次都自行實作依賴注入當 codebase 一大成本也會相當高,因此能不能有一個統一的介面來實做這件事呢?今天就要來介紹 InversifyJS 這個用來實作 IOC 的第三方套件。
首先先來看看 InversifyJS官網 是怎麼介紹自己的
A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
它透過一個 'IOC container' 來統一管理類別的注入。
多說無益,直接用程式碼示範吧!
先來架構出尚未使用 InversifyJS 的程式碼
專案架構
- root
- main.ts
- service.ts
- dependencies.ts
main.ts 是建構出 service 實例的地方
service.ts 則是需要注入類別的 class
dependencies.ts 則存在兩個即將被 service 注入的類別
// dependencied.ts
export class DependencyA {
private readonly name: string = 'dependencyA';
public getName(): string {
return this.name;
}
}
export class DependencyB {
private readonly name: string = 'dependencyB';
public getName(): string {
return this.name;
}
}
// service.ts
import { DependencyA, DependencyB } from './dependencies';
export class KyleService {
protected depA: DependencyA;
protected depB: DependencyB;
constructor() {
this.depA = new DependencyA();
this.depB = new DependencyB();
}
public getAllNames(): string[] {
return [this.depA.getName(), this.depB.getName()];
}
}
// main.ts
import { KyleService } from './service';
const service: KyleService = new KyleService();
console.log(service.getAllNames());
接著我們試著投入 InversifyJS 的懷抱吧!
// 安裝 InversifyJS 與 reflect-metadata(compile 用)
$ yarn add -D inversify reflect-metadata
接著在 tsconfig.json 中新增以下幾行,讓 TypeScript 能夠支援 InversifyJS
"compilerOptions": {
...,
"types": ["reflect-metadata"],
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
以上都完成後就能在程式碼中使用 Inversify 啦
首先創建 di-container.ts 並在其中創建管理 DI 的 container
import { Container } from 'inversify';
import { DependencyA, DependencyB } from './dependencies';
let IOCContainer = new Container();
// instructs the container to return an instance of class whenever a particular class is requested (injected).
IOCContainer.bind<DependencyA>(DependencyA).toSelf();
IOCContainer.bind<DependencyB>(DependencyB).toSelf();
export default IOCContainer;
接著用 TS decorator 讓 dependency 標示為 injectable
// dependencies.ts
import { injectable } from 'inversify';
@injectable()
export class DependencyA {
private readonly name: string = 'dependencyA';
public getName(): string {
return this.name;
}
}
@injectable()
export class DependencyB {
private readonly name: string = 'dependencyB';
public getName(): string {
return this.name;
}
}
最後修改 service.ts 與 main.ts
// service.ts
import { inject, injectable } from 'inversify';
import { DependencyA, DependencyB } from './dependencies';
@injectable()
export class KyleService {
protected depA: DependencyA;
protected depB: DependencyB;
constructor(
@inject(DependencyA) dependencyA: DependencyA,
@inject(DependencyA) dependencyB: DependencyB
) {
this.depA = dependencyA;
this.depB = dependencyB;
}
public getAllNames(): string[] {
return [this.depA.getName(), this.depB.getName()];
}
}
// main.ts
import { KyleService } from './service';
import IOCContainer from './di-container';
const service: KyleService = IOCContainer.resolve<KyleService>(KyleService);
console.log(service.getAllNames());
如此一來 code 就比原本自己實作 IOC 時更為直覺,介面也更為統一了。
藉由兩篇的篇幅介紹了 IOC 的概念,雖然不是一項 backend 限定的技術,然而卻是非常重要的撰寫優質程式碼的方法,尤其前後端開發有很大的機會會使用 OOP 的方式開發,如何撰寫易擴展、易維護的程式碼,朝更好的 OOP 走去,是這兩篇文章想分享給大家的。
想盡辦法當好一個Junior Backend Developer
用舒服的姿勢開發 Python Project